<?php
/*
 * MiniMe - a minimalistic OStatus complient microblogging tool
 * Copyright (C) 2010, Tuomas Koski - koski.tuomas@gmail.com
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * This class will represent xml more or less like this:
 * 
 * <?xml version="1.0" encoding="UTF-8"?>
 * <XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
 *    <Subject>acct:tkoski@identi.ca</Subject>
 *    <Alias>http://identi.ca/user/52467</Alias>
 *    <Link rel="http://schemas.google.com/g/2010#updates-from" type="application/atom+xml" href="http://identi.ca/api/statuses/user_timeline/52467.atom"/>
 *    <Link rel="http://salmon-protocol.org/ns/salmon-replies" href="http://identi.ca/main/salmon/user/52467"/>
 *    <Link rel="magic-public-key" href="data:application/magic-public-key,RSA.oSdSbJ99WDC0zRUpk41bpI42FarMo-o6JxJKEeKCPSU1SW9kdXdAUPhWu0JVwdF5rDXWijXaOcdZ3utGwk0pmKxsX6MEQg54L4rfIzWZiHz9OUGgDx9R4tXpm38CXOGfpu4Sx2lmeYVxIii32P32EPJHyZN5Zi9Sr_8zSbXYnM8=.AQAB"/>
 * </XRD>
 * 
 * @author     Tuomas Koski <koski.tuomas@gmail.com>
 * @copyright  Tuomas Koski
 * @license    http://www.gnu.org/licenses/lgpl.txt
 */

class Xrd_Lrdd {
    
    const ELEMENT_XRD = "XRD";
    
    /**
     * @var Xrd_Lrdd_Subject
     */
    private $subject;
    
    /**
     * @var Xrd_Lrdd_Alias
     */
    private $alias;
    
    private $links = array();
    
    const KEY_SUBJECT          = "subject";
    const KEY_ALIAS            = "alias";
    const KEY_ATOM             = "atom";
    const KEY_SALMON           = "salmon";
    const KEY_MAGIC_PUBLIC_KEY = "magic-public-key";
    const KEY_WEBFINGER_PROFILE_PAGE = "http://webfinger.net/rel/profile-page";
    
    private function __construct($subject, $alias) {
        $this->subject = $subject;
        $this->alias = $alias;
    }
    
    private function setSubject(Xrd_Ldrr_Subject $subject) {
        if($subject == null) {
            throw new Exception("Subject cannot be set to null.");
        }
        $this->subject = $subject;
    }
    
    private function setAlias(Xrd_Ldrr_Alias $alias) {
        if($alias == null) {
            throw new Exception("Alias cannot be set to null.");
        }
        $this->alias = $alias;
    }
    
    private function addLink($link) {
//        God damn PHP! Why does this not work?
//        if( !($link instanceof Xrd_Ldrr_Link) ) {
//            throw new Exception("Argument 1 passed to Xrd_Lrdd::addLink() must be an instance of Xrd_Ldrr_Link! " . get_class($link) . " given.");
//        }
        $this->links[$link->getRel()] = $link;
    }
    
    /**
     * 
     * @return Xrd_Lrdd_Link 
     */
    public function getLinkAtom() {
        return $this->getLinkWithKey(Xrd_Lrdd_Link_Atom::REL_VALUE);
    }
    
    /**
     * 
     * @return Xrd_Lrdd_Link 
     */
    public function getLinkSalmon() {
        return $this->getLinkWithKey(Xrd_Lrdd_Link_Salmon::REL_VALUE);
    }
    
    /**
     * 
     * @return Xrd_Lrdd_Link 
     */
    public function getLinkMagicPublicKey() {
        return $this->getLinkWithKey(Xrd_Lrdd_Link_MagicPublicKey::REL_VALUE);
    }
    
	/**
     * 
     * @return Xrd_Lrdd_Link 
     */
    public function getLinkWebfingerProfilePage() {
        return $this->getLinkWithKey(Xrd_Lrdd_Link_WebfingerProfilePage::REL_VALUE);
    }
    
    /**
     * 
     * @return Xrd_Lrdd_Link 
     */
    private function getLinkWithKey($key) {
        if(isset($this->links[$key])) {
            return $this->links[$key];
        }
        return null;
    }
    
    /**
     * Builind funrgion will try to create LRDD object from XML.
     * 
     * Example XML expected similar to this:
     * <?xml version="1.0" encoding="UTF-8"?>
	 * <XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
  	 *    <Subject>acct:tkoski@identi.ca</Subject>
  	 *    <Alias>http://identi.ca/user/52467</Alias>
  	 *    <Link rel="http://webfinger.net/rel/profile-page" type="text/html" href="http://identi.ca/user/52467"/>
  	 *    <Link rel="http://schemas.google.com/g/2010#updates-from" type="application/atom+xml" href="http://identi.ca/api/statuses/user_timeline/52467.atom"/>
  	 *    <Link rel="http://microformats.org/profile/hcard" type="text/html" href="http://identi.ca/tkoski/hcard"/>
  	 *    <Link rel="http://gmpg.org/xfn/11" type="text/html" href="http://identi.ca/user/52467"/>
  	 *    <Link rel="describedby" type="application/rdf+xml" href="http://identi.ca/tkoski/foaf"/>
  	 *    <Link rel="http://salmon-protocol.org/ns/salmon-replies" href="http://identi.ca/main/salmon/user/52467"/>
  	 *    <Link rel="http://salmon-protocol.org/ns/salmon-mention" href="http://identi.ca/main/salmon/user/52467"/>
  	 *    <Link rel="magic-public-key" href="data:application/magic-public-key,RSA.oSdSbJ99WDC0zRUpk41bpI42FarMo-o6JxJKEeKCPSU1SW9kdXdAUPhWu0JVwdF5rDXWijXaOcdZ3utGwk0pmKxsX6MEQg54L4rfIzWZiHz9OUGgDx9R4tXpm38CXOGfpu4Sx2lmeYVxIii32P32EPJHyZN5Zi9Sr_8zSbXYnM8=.AQAB"/>
  	 *    <Link rel="http://ostatus.org/schema/1.0/subscribe" template="http://identi.ca/main/ostatussub?profile={uri}"/>
  	 * </XRD>
     * 
     * @param String $rawXml
     * @return Xrd_Lrdd
     */
    public static function buildFromRawXML($rawXml) {
        $dom = new DOMDocument();
        if(@$dom->loadXML($rawXml)) {
            return self::buildFromDom($dom);
        }
        throw new Exception("Invalid raw XML data provided!");
    }
    
    public static function buildFromDom(DOMDocument $dom) {
        $xrd_element = $dom->getElementsByTagName('XRD')->item(0);
        if (!$xrd_element) {
            throw new Exception("Invalid DOM object for XRD, missing XRD root");
        }

        $subject = $xrd_element->getElementsByTagName('Subject')->item(0);
        if ($subject) { 
            //throw new Exception("Invalid DOM object for XRD, missing Subject value.");
            $subject = Xrd_Lrdd_Subject::buildFromDOMElement($subject);
        } else {
            $subject = new Xrd_Lrdd_Subject("N/A");
        }
        
        $alias = $xrd_element->getElementsByTagName('Alias')->item(0);
        if ($alias) { 
            //throw new Exception("Invalid DOM object for XRD, missing Alias value.");
            $alias = Xrd_Lrdd_Alias::buildFromDOMElement($alias);
        } else {
            $alias = new Xrd_Lrdd_Alias("N/A");
        }
        
        $linkSalmon = new Xrd_Lrdd_Link_Empty(Xrd_Lrdd_Link_Salmon::REL_VALUE);
        $linkMagic = new Xrd_Lrdd_Link_Empty(Xrd_Lrdd_Link_MagicPublicKey::REL_VALUE);
        $linkAtom = new Xrd_Lrdd_Link_Empty(Xrd_Lrdd_Link_Atom::REL_VALUE);
        $linkWebfingerProfilePage = new Xrd_Lrdd_Link_Empty(Xrd_Lrdd_Link_WebfingerProfilePage::REL_VALUE);
        
        foreach ($xrd_element->getElementsByTagName(Xrd_Lrdd_Link::ELEMENT_LINK) as $elm) {

            // this is quite a hack.
            if( $elm->parentNode->nodeName !== self::ELEMENT_XRD) {
                continue;
            }
            
            if($elm->getAttribute(Xrd_Lrdd_Link::ATTRIBUTE_REL) == Xrd_Lrdd_Link_Atom::REL_VALUE) {
                $linkAtom = Xrd_Lrdd_Link_Atom::buildFromDOMElement($elm); 
                     
            } else if($elm->getAttribute(Xrd_Lrdd_Link::ATTRIBUTE_REL) == Xrd_Lrdd_Link_Salmon::REL_VALUE) {
                $linkSalmon = Xrd_Lrdd_Link_Salmon::buildFromDOMElement($elm);  
                    
            } else if($elm->getAttribute(Xrd_Lrdd_Link::ATTRIBUTE_REL) == Xrd_Lrdd_Link_MagicPublicKey::REL_VALUE) {
                $linkMagic = Xrd_Lrdd_Link_MagicPublicKey::buildFromDOMElement($elm);
                      
            } else if($elm->getAttribute(Xrd_Lrdd_Link::ATTRIBUTE_REL) == Xrd_Lrdd_Link_WebfingerProfilePage::REL_VALUE) {
                $linkWebfingerProfilePage = Xrd_Lrdd_Link_WebfingerProfilePage::buildFromDOMElement($elm);
                      
            }
        }
        
        // This is again elgg hack.
        foreach ($xrd_element->getElementsByTagName("link") as $elm) {

            // this is quite a hack.
            if( $elm->parentNode->nodeName !== self::ELEMENT_XRD) {
                continue;
            }
            
            if($elm->getAttribute(Xrd_Lrdd_Link::ATTRIBUTE_REL) == Xrd_Lrdd_Link_Atom::REL_VALUE) {
                $linkAtom = Xrd_Lrdd_Link_Atom::buildFromDOMElement($elm); 
                     
            } else if($elm->getAttribute(Xrd_Lrdd_Link::ATTRIBUTE_REL) == Xrd_Lrdd_Link_Salmon::REL_VALUE) {
                $linkSalmon = Xrd_Lrdd_Link_Salmon::buildFromDOMElement($elm);  
                    
            } else if($elm->getAttribute(Xrd_Lrdd_Link::ATTRIBUTE_REL) == Xrd_Lrdd_Link_MagicPublicKey::REL_VALUE) {
                $linkMagic = Xrd_Lrdd_Link_MagicPublicKey::buildFromDOMElement($elm);
                      
            } else if($elm->getAttribute(Xrd_Lrdd_Link::ATTRIBUTE_REL) == Xrd_Lrdd_Link_WebfingerProfilePage::REL_VALUE) {
                $linkWebfingerProfilePage = Xrd_Lrdd_Link_WebfingerProfilePage::buildFromDOMElement($elm);
                      
            }
        }
        
        $lrdd = new Xrd_Lrdd($subject, $alias);
        $lrdd->addLink($linkAtom);
        $lrdd->addLink($linkSalmon);
        $lrdd->addLink($linkMagic);
        $lrdd->addLink($linkWebfingerProfilePage);
        
        return $lrdd;
    }
    
    public function asXML() {
        $dom = new DOMDocument('1.0', 'UTF-8');
        $dom->formatOutput = true;
        
        $xrd_element = $dom->createElementNS(Xrd::XRD_NS, self::ELEMENT_XRD);

        $xrd_element->appendChild($this->subject->asDomElement($dom));
        $xrd_element->appendChild($this->alias->asDomElement($dom));
        
        foreach ($this->links as $link) {
            $xrd_element->appendChild($link->asDomElement($dom));
        } 
        
        $dom->appendChild($xrd_element);
        
        return $dom->saveXML();
    }
    
    /**
     * 
     * @param $array
     * @return Xrd_Lrdd
     */
    public static function buildFromArray(Array $array) {
        
        $subject = new Xrd_Lrdd_Subject($array[self::KEY_SUBJECT]);
        $alias = new Xrd_Lrdd_Alias($array[self::KEY_ALIAS]);
        
        $lrdd = new Xrd_Lrdd($subject, $alias);
        $lrdd->addLink(new Xrd_Lrdd_Link_WebfingerProfilePage($array[self::KEY_WEBFINGER_PROFILE_PAGE]));
        $lrdd->addLink(new Xrd_Lrdd_Link_Atom($array[self::KEY_ATOM]));
        $lrdd->addLink(new Xrd_Lrdd_Link_Salmon($array[self::KEY_SALMON]));
        $lrdd->addLink(new Xrd_Lrdd_Link_SalmonSalmon($array[self::KEY_SALMON]));
        $lrdd->addLink(new Xrd_Lrdd_Link_SalmonMention($array[self::KEY_SALMON]));
        $lrdd->addLink(new Xrd_Lrdd_Link_MagicPublicKey($array[self::KEY_MAGIC_PUBLIC_KEY]));
        
        return $lrdd;
    }
}